// Copyright 2022 Huawei Cloud Computing Technology Co., Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "CaeSignUtil.h"
#include <iostream>
#include <string>
#include "CaeLogConfig.h"
#include "CaeCommon.h"
#include "../cae_3rd/base64/base64.h"
#include "../hwsecure/securec.h"
#include "../libs/openssl/include/openssl/rsa.h"

using namespace std;

CaeSignUtil::CaeSignUtil()
{
    this->m_privateKey = nullptr;
}

CaeSignUtil::~CaeSignUtil()
{
    if (nullptr != m_privateKey) {
        EC_KEY_free(m_privateKey);
    }
}

bool CaeSignUtil::InitSha256withRSASigner()
{
    if (nullptr != m_privateKey) {
        return true;
    }
    if (!SetPriKey()) {
        LOGE("Init Sha256withRSASigner ERROR,set m_privateKey failed!");
        return false;
    }
    return true;
}

bool CaeSignUtil::CreateSignature(CaeRequestParams *request, time_t validDateTime)
{
    std::string validTime = request->InitHeaders(validDateTime);
    if (strlen(validTime.c_str()) == 0) {
        return false;
    }
    std::string stringToSign = GetStringToSign(request->GetPayload(), validTime);
    if (strlen(stringToSign.c_str()) == 0) {
        return false;
    }
    std::string infoSignedStr = "";
    string &signedInfo = infoSignedStr;
    if (!Sha256RsaSign(stringToSign, signedInfo)) {
        return false;
    }
    request->AddHeader("Signature", infoSignedStr);
    return true;
}

const string CaeSignUtil::GetStringToSign(string body, string validDateTime)
{
    if (strlen(body.c_str()) == 0) {
        LOGE("RequestBody is NULL.");
        return "";
    }
    if (strlen(validDateTime.c_str()) == 0) {
        LOGE("Request_validDateTime is NULL.");
        return "";
    }
    return body + "\n" + "valid_date_time" + ":" + validDateTime;
}

bool CaeSignUtil::CAESha256(const string& tmpVerifyStr, string &encodedStr)
{
    char buf[4] = {0};
    uint8_t hash[SHA256_DIGEST_LENGTH] = {0};
    SHA256_CTX sha256Ctx;
    if (1 != SHA256_Init(&sha256Ctx)) {
        LOGE("SHA256_Init failed");
        return false;
    }
    if (1 != SHA256_Update(&sha256Ctx, tmpVerifyStr.c_str(), tmpVerifyStr.length())) {
        LOGE("SHA256_Update failed");
        return false;
    }
    if (1 != SHA256_Final(hash, &sha256Ctx)) {
        LOGE("SHA256_Final failed");
        return false;
    }

    for (uint8_t i : hash) {
        int ret = snprintf_s(buf, sizeof(buf), (sizeof(buf) - 1), "%02x", i);
        if (ret == -1) {
            LOGE("Failed to snprintf buf.");
            return false;
        }
        encodedStr += buf;
        ret = memset_s(buf, sizeof(buf), '\0', sizeof(buf));
        if (ret != EOK) {
            LOGE("Failed to memset buf.");
            return false;
        }
    }
    return true;
}

bool CaeSignUtil::SetPriKey()
{
    this->m_privateKey = EC_KEY_new();
    if (nullptr == m_privateKey) {
        LOGE("EC_KEY_new failed.");
        return false;
    }

    std::string cae_key = CaeConfigManage::GetPrivKeyPath();
    if (cae_key.empty()) {
        LOGE("CaeKeyDir is invalid.");
        EC_KEY_free(m_privateKey);
        m_privateKey = nullptr;
        return false;
    }

    BIO *priio;
    priio = BIO_new_file(cae_key.data(), "rb");
    if (nullptr == priio) {
        LOGE("BIO_new_file failed.");
        EC_KEY_free(m_privateKey);
        m_privateKey = nullptr;
        return false;
    }

    std::string certUserData = CaeConfigManage::GetCertUserData();
    if (!certUserData.empty()) {
        LOGI("Set cert user data.");
        this->m_privateKey = PEM_read_bio_ECPrivateKey(priio, &m_privateKey, nullptr, (void *)certUserData.c_str());
    } else {
        LOGI("No cert user data.");
        this->m_privateKey = PEM_read_bio_ECPrivateKey(priio, &m_privateKey, nullptr, nullptr);
    }

    BIO_free(priio);

    if (nullptr == m_privateKey) {
        LOGE("Get ECKey failed.");
        return false;
    }
    return true;
}

bool CaeSignUtil::Sha256RsaSign(const std::string& msg, std::string &signedMsg)
{
    uint8_t *encStr = nullptr;
    EVP_MD_CTX *m_RSASignCtx = EVP_MD_CTX_create();
    EVP_PKEY *priKey = EVP_PKEY_new();
    size_t encLen = 0;
    if (nullptr == this->m_privateKey) {
        LOGE("Private key is NULL.");
        Clear(encStr, m_RSASignCtx, priKey);
        return false;
    }

    EVP_PKEY_assign_EC_KEY(priKey, m_privateKey);

    if (EVP_DigestSignInit(m_RSASignCtx, nullptr, EVP_sha256(), nullptr, priKey) <= 0) {
        LOGE("EVP_DigestSignInit error.");
        Clear(encStr, m_RSASignCtx, priKey);
        return false;
    }
    if (EVP_DigestSignUpdate(m_RSASignCtx, reinterpret_cast<const uint8_t *>(msg.data()), msg.length()) <= 0) {
        LOGE("EVP_DigestSignUpdate error.");
        Clear(encStr, m_RSASignCtx, priKey);
        return false;
    }
    if (EVP_DigestSignFinal(m_RSASignCtx, nullptr, &encLen) <= 0) {
        LOGE("Determine encLen failed.");
        Clear(encStr, m_RSASignCtx, priKey);
        return false;
    }

    const size_t maxLength = 1024 * 10;
    if (encLen <= 0 || encLen > maxLength) {
        LOGE("Invalid mem size encLen %d.", encLen);
        Clear(encStr, m_RSASignCtx, priKey);
        return false;
    } else {
        encStr = (uint8_t *)malloc(encLen);
        if (encStr == nullptr) {
            LOGE("EncStr is null.");
            Clear(encStr, m_RSASignCtx, priKey);
            return false;
        }
    }

    if (EVP_DigestSignFinal(m_RSASignCtx, encStr, &encLen) <= 0) {
        LOGE("SignedMsg error.");
        Clear(encStr, m_RSASignCtx, priKey);
        return false;
    }

    signedMsg = base64_encode((const unsigned char *) encStr, (unsigned int) encLen);
    Clear(encStr, m_RSASignCtx, priKey);
    return true;
}

void CaeSignUtil::Clear(uint8_t *encStr, EVP_MD_CTX *rsaSignCtx, EVP_PKEY *priKey)
{
    if (encStr != nullptr) {
        free(encStr);
    }
    if (nullptr != rsaSignCtx) {
        OPENSSL_free(rsaSignCtx);
    }
    if (nullptr != priKey) {
        OPENSSL_free(priKey);
    }
}